from config import *
from autoplayer.checkmate import Checkmater
from autoplayer.neighbour import has_neighbour
from game.boardModel import BoardModel
from autoplayer.zobrist import Zobrist

from copy import deepcopy


from random import randint


class Searcher:
    def __init__(self):
        self._cache = {}
        self.role = None
        self.model = None
        self.simulate_model = None
        self.max_score = 0
        self.min_score = 0
        self.evaluator = None
        self.checkmater = None
        self.zobrist = None

    def setup(self, role, model, zobrist, evaluator, checkmater):
        self._cache = {}
        self.role = role
        self.model = model
        self.simulate_model = deepcopy(model)
        self.simulate_model.simulate = True
        self.max_score = SCORE.FIVE * 10
        self.min_score = - SCORE.FIVE * 10
        self.evaluator = evaluator
        self.checkmater = checkmater
        self.zobrist = zobrist

    def get_candidates(self):  # search candidates
        fives = []
        fours = []
        blocked_fours = []
        double_threes = []
        threes = []
        twos = []
        neighbours = []
        for i in range(0, Len):
            for j in range(0, Len):
                if self.model.get_pos(i, j) != ROLES.vacant:
                    continue
                if not has_neighbour(self.model, i, j, 1, 1):
                    continue
                man_score = self.evaluator.manplayer_score[i][j]
                auto_score = self.evaluator.autoplayer_score[i][j]
                if auto_score > SCORE.FIVE:
                    return [[i, j]]
                elif man_score > SCORE.FIVE:
                    fives.append([i, j])
                elif auto_score > SCORE.FOUR:
                    fours.insert(0, [i, j])
                elif man_score > SCORE.FOUR:
                    fours.append([i, j])
                elif auto_score > SCORE.BLOCKED_FOUR:
                    blocked_fours.insert(0, [i, j])
                elif man_score > SCORE.BLOCKED_FOUR:
                    blocked_fours.append([i, j])
                elif auto_score > SCORE.THREE * 2:
                    double_threes.insert(0, [i, j])
                elif man_score > SCORE.THREE * 2:
                    double_threes.append([i, j])
                elif auto_score > SCORE.THREE:
                    threes.insert(0, [i, j])
                elif man_score > SCORE.THREE:
                    threes.append([i, j])
                elif auto_score > SCORE.TWO:
                    twos.insert(0, [i, j])
                elif man_score > SCORE.TWO:
                    twos.append([i, j])
                else:
                    neighbours.append([i, j])
        if fives.__len__() > 0:
            return fives
        if fours.__len__() > 0:
            return fours
        if blocked_fours.__len__() > 0:
            return blocked_fours
        if double_threes.__len__() > 0:
            return double_threes + threes
        result = threes + twos + neighbours
        return result[0:MAX_COUNT]

    def deep_search(self, deep, alpha, beta, role):
        # cache
        if CACHE:
            cache_val = self._cache[self.evaluator.zobrist.code]
            if cache_val and cache_val['deep'] >= deep:
                return cache_val['score']
        value = self.evaluator.evaluate(role)
        # deep == 0 or fatal situation
        if deep <= 0 or value * THRESHOLD >= SCORE.FIVE:
            return value
        best = self.min_score
        points = self.get_candidates()
        for point in points:
            x = point[0]
            y = point[1]
            self.simulate_model.new_step(x, y)
            if best > alpha:
                alpha_argu = -best
            else:
                alpha_argu = -alpha
            value = self.deep_search(deep - 1, -beta, alpha_argu, opposite_role(role))
            value = - value * DEEP_DESCEND
            self.simulate_model.retract()
            if value > THRESHOLD * best:
                best = value
            if value * THRESHOLD > beta:  # alpha-beta cut
                self.cache(deep, value)
                return value
            # checkmate
            if 1 < deep < 4 and best * THRESHOLD < SCORE.THREE * 2 and best > -SCORE.THREE * THRESHOLD:
                check = self.checkmater.check(role, CHECKMATE_DEEP)
                if check:
                    score = check['score'] * (0.8 ** check['points'].__len__())
                    if role != self.role:
                        score = -score
                    self.cache(deep, score)
                    return score
            self.cache(deep, best)
            return best
    # new_step(role) ???

    def broad_search(self, deep):
        best = self.min_score
        points = self.get_candidates()
        best_points = []
        for point in points:
            x = point[0]
            y = point[1]
            self.simulate_model.new_step(x, y)
            value = self.deep_search(deep - 1, -self.max_score, -best, self.role)
            # descend border
            if x < 3 or x > 11 or y < 3 or y > 11:
                value *= 0.2
            point.append(value)
            if value * THRESHOLD >= best and value <= best * THRESHOLD:
                best_points.append(point)
            # better point
            if value >= best * THRESHOLD:
                best = value
                best_points = [point]
            self.simulate_model.retract()
        l = best_points.__len__()
        rand_index = randint(0, l - 1)
        return best_points[rand_index]

    def cache(self, deep, score):
        if not CACHE:
            return
        self._cache[self.zobrist.code] = {
            'deep': deep,
            'score': score
        }

    def deepen_search(self, deep=SEARCH_DEEP):
        for i in range(2, deep + 1):
            result = self.broad_search(i)
            if result[2] * THRESHOLD > SCORE.FOUR:
                return result
        return result

    def search(self, deep=SEARCH_DEEP):
        self.simulate_model = deepcopy(self.model)
        self.simulate_model.simulate = True
        return self.deepen_search(deep)
